﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using GDI.Annotations;

namespace GDI
{
    /// <summary>Печатаемый объект</summary>
    public abstract class PrintableItem : INotifyPropertyChanged
    {
        /* -------------------------------------------------------------------------------------------- */

        private static SmoothingMode sf_globalSmoothing = SmoothingMode.HighSpeed;

        /* -------------------------------------------------------------------------------------------- */

        public static SmoothingMode GlobalSmoothing
        {
            get { return sf_globalSmoothing; }
            set { sf_globalSmoothing = value; }
        }

        /* -------------------------------------------------------------------------------------------- */

        #region События

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
        {
            var lv_Handler = PropertyChanged;
            if(lv_Handler != null)
                lv_Handler(this, new PropertyChangedEventArgs(PropertyName));
        }

        public event EventHandler Resize;

        protected virtual void OnResize(EventArgs e)
        {
            var lv_Handlers = Resize;
            if(lv_Handlers != null)
                Resize.Invoke(this, e);
        }

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        #region Поля

        /// <summary>Видимоть объекта</summary>
        private bool f_Visible = true;

        /// <summary>Угол поворота</summary>
        private float f_Angle;

        /// <summary>Положение объекта</summary>
        private PointF f_Position;

        private SizeF f_Scale = new SizeF(1, 1);

        private SmoothingMode f_SmoothingMode = SmoothingMode.Default;

        protected Func<PointF, PointF> f_Converter_ToScreen;
        protected Func<PointF, PointF> f_Converter_FromScreen;

        private Region f_ClipRegion = null;
        private string f_Name;

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        #region Свойства

        public string Name
        {
            get { return f_Name; }
            set
            {
                if(value == f_Name) return;
                f_Name = value;
                OnPropertyChanged();
            }
        }

        /// <summary>Видимость объекта</summary>
        public bool Visible
        {
            get { return f_Visible; }
            set
            {
                if(value == f_Visible) return;
                f_Visible = value;
                OnPropertyChanged();
            }
        }

        /// <summary>Угол поворота</summary>
        public float Angle
        {
            get { return f_Angle; }
            set
            {
                if(Math.Abs(value - f_Angle) < float.Epsilon) return;
                f_Angle = value;
                OnPropertyChanged();

            }
        }

        /// <summary>Угол поворота в градусах</summary>
        public double AngleDeg
        {
            get { return f_Angle * 180 / Math.PI; }
            set
            {
                f_Angle = (float)(value * Math.PI / 180);
                OnPropertyChanged();
            }
        }

        /// <summary>Положение объекта</summary>
        public virtual PointF Position
        {
            get { return f_Position; }
            set
            {
                if(value == f_Position) return;
                f_Position = value;
                OnPropertyChanged();
            }
        }

        public SizeF Scale
        {
            get { return f_Scale; }
            set
            {
                if(value == f_Scale) return;
                f_Scale = value;
                OnPropertyChanged();
            }
        }

        public virtual SmoothingMode Smoothing
        {
            get { return f_SmoothingMode; }
            set
            {
                if(f_SmoothingMode == value) return;
                f_SmoothingMode = value;
                OnPropertyChanged();
            }
        }

        public Func<PointF, PointF> ConverterToScreen
        {
            get { return f_Converter_ToScreen; }
            set
            {
                if(f_Converter_ToScreen == value) return;
                f_Converter_ToScreen = value;
                OnPropertyChanged();
            }
        }

        public Func<PointF, PointF> ConverterFromScreen
        {
            get { return f_Converter_FromScreen; }
            set
            {
                if(f_Converter_FromScreen == value) return;
                f_Converter_FromScreen = value;
                OnPropertyChanged();
            }
        }

        public Region ClipRegion { get { return f_ClipRegion; } set { f_ClipRegion = value; OnPropertyChanged(); } }

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        protected PrintableItem() { f_SmoothingMode = sf_globalSmoothing; }

        protected PrintableItem(PointF Position) : this() { f_Position = Position; }
        protected PrintableItem(PointF Position, float Angle) : this() { f_Position = Position; f_Angle = Angle; }

        protected PrintableItem(float x, float y) : this(new PointF(x, y)) { }
        protected PrintableItem(float x, float y, float Angle) : this(new PointF(x, y), Angle) { }


        /* -------------------------------------------------------------------------------------------- */

        public virtual void SetEventsTo(Control c)
        {
            c.Paint += OnPaint;
            c.Resize += OnResize;
        }

        public virtual void ResetEventsTo(Control c)
        {
            c.Paint -= OnPaint;
            c.Resize -= OnResize;
        }

        /* -------------------------------------------------------------------------------------------- */

        public virtual void OnResize(object Sender, EventArgs e) { OnResize(e); }

        #region Методы отрисовки объекта

        /// <summary>Обработчик события отрисовки объекта</summary>
        /// <param name="Sender">Источник события отрисовки</param><param name="Args">Аргументы события отрисовки</param>
        public void OnPaint(object Sender, PaintEventArgs Args) { DrawObject(Args.Graphics); }

        /// <summary>Нарисовать объект на канве</summary><param name="Canva">Графический контент</param>
        public void DrawObject(Graphics Canva)
        {
            if(!f_Visible) return;

            var container = Canva.BeginContainer();

            var lv_SmoothingMode = Canva.SmoothingMode;
            if(f_SmoothingMode != SmoothingMode.Default)
                Canva.SmoothingMode = f_SmoothingMode;

            var lv_Angle = f_Angle;
            var point = f_Position;
            var x = point.X;
            var y = point.Y;
            var sx = f_Scale.Width;
            var sy = f_Scale.Height;

            //Задан угол поворота, отличный от нуля
            var rotate = Math.Abs(lv_Angle) > float.Epsilon;
            //Задано смещение объекта, отличное от нуля
            var translate = Math.Abs(x) > float.Epsilon || Math.Abs(y) > float.Epsilon;

            if(Math.Abs(f_Scale.Width - 1) > float.Epsilon || Math.Abs(f_Scale.Height - 1) > float.Epsilon)
                Canva.ScaleTransform(sx, sy);

            if(translate) Canva.TranslateTransform(x, y);
            if(rotate) Canva.RotateTransform(f_Angle);

            Region lv_OldClipRegion = null;
            if(f_ClipRegion != null)
            {
                lv_OldClipRegion = Canva.Clip;
                Canva.Clip = f_ClipRegion;
            }

            //Отрисовать объект
            Draw(Canva);

            if(lv_OldClipRegion != null)
                Canva.Clip = lv_OldClipRegion;

            if(f_SmoothingMode != SmoothingMode.Default)
                Canva.SmoothingMode = lv_SmoothingMode;

            Canva.EndContainer(container);
        }

        /// <summary>Метод отрисовки</summary><param name="g">Графический контент</param>
        protected abstract void Draw(Graphics g);

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Неявный оператор приведения типа отрисовываемого объекта к методу обработчика события отрисовки</summary>
        /// <param name="Item">Рисуемый объект</param><returns>Обработчик события отрисовки</returns>
        public static implicit operator PaintEventHandler(PrintableItem Item) { return Item.OnPaint; }

        public static Control operator +(Control c, PrintableItem Item)
        {
            Item.SetEventsTo(c);
            return c;
        }

        public static Control operator -(Control c, PrintableItem Item)
        {
            Item.ResetEventsTo(c);
            return c;
        }

        /* -------------------------------------------------------------------------------------------- */

    }
}
